#pragma managed(push, off)
#include <windows.h>
#include <string.h>
#include "sqlite3.h"
#include "internals.h"
#pragma managed(pop)

#include <vcclr.h>

#include "DatabaseImpl.h"
// Copyright (c) 2010 Thomas Stuefe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.


#include "StatementImpl.h"
#include "SqliteException.h"

using namespace System;


#define FAIL_IF_CLOSED \
	if (!_hdl) { \
		trcError(L"Database already closed."); \
		throw gcnew SqliteException(this, nullptr, -1, NULL, L"Database already closed."); \
	} 


namespace zencat {
namespace sqlite {

	DatabaseImpl::DatabaseImpl(::sqlite3* hdl, String^ databaseFile, bool wasCreated)
		:	_hdl(hdl)
		,	_databaseFile(databaseFile)
		,	_inTransaction(false)
		,	_wasCreated(wasCreated)
	{
		asrt(_hdl);
		asrt(databaseFile != nullptr);

		// for tracing purposes keep a not-managed version fo the database file name around
		pin_ptr<const wchar_t> pinned_databaseFile = PtrToStringChars(databaseFile);
		_databaseFile0 = _wcsdup(pinned_databaseFile);

	}

	DatabaseImpl::~DatabaseImpl() {
		if (_hdl) {
			this->Close();
		}
	}

	DatabaseImpl::!DatabaseImpl() {
		if (_hdl) {
			this->Close();
		}
	}

	void DatabaseImpl::Close() {
		
		FAIL_IF_CLOSED;

		trcInfo(L"Closing Database (0x%p, \"%s\")...", _hdl, _databaseFile0);

		const int rc = ::sqlite3_close(_hdl);

		if (rc != SQLITE_OK) {
			trcSqliteError(rc, NULL, L"Database Close failed (0x%p, \"%s\").", _hdl, _databaseFile0);
			SqliteException^ exc = gcnew SqliteException(this, nullptr, rc, NULL, L"Database Close Failed.");
			throw exc;
		}

		_hdl = NULL;
		_databaseFile = nullptr;
		_inTransaction = false;

		::free( (void*) _databaseFile0); 
		_databaseFile0 = NULL;

		trcInfo(L"Database closed.");

	}

	ErrorCode DatabaseImpl::LastErrorCode::get() {
		
		FAIL_IF_CLOSED;

		const int raw = ::sqlite3_extended_errcode(_hdl);
		ErrorCode errcode = errorCodeFromRawCode(raw);
		return errcode;

	}

	String^ DatabaseImpl::LastErrorMessage::get() { 
		
		FAIL_IF_CLOSED;

		const wchar_t* const txt = (wchar_t*) ::sqlite3_errmsg16(_hdl);
		return gcnew String(txt);		

	}

	int DatabaseImpl::NumberOfChangedRows::get() { 

		FAIL_IF_CLOSED;

		// huh .. Why not 64bit? is it not possible to change more than 2^32 rows?
		const int rc = ::sqlite3_changes(_hdl);

		trcInfo(L"%d rows changed.", rc);

		return rc;

	}

	__int64 DatabaseImpl::LastInsertRowId::get() {
	
		FAIL_IF_CLOSED;

		const __int64 rc = ::sqlite3_last_insert_rowid(_hdl);

		trcInfo(L"Last inserted row id: %I64d", rc);

		return rc;
	
	}

	void DatabaseImpl::ExecuteSql(String^ sql) {

		FAIL_IF_CLOSED;

		// The way I implement this is - because there is no sqlite_exec16 convenience function for UTF16,
		// like there is for ascii (sqlite_exec) - by creating a statement, executing it, and releasing it. 
		
		IStatement^ statement = this->CreateStatement(sql);
		guarantee(statement != nullptr); // createPreparedStatement should throw if creation fails
		statement->Step();
		statement->Close();

	}

	IStatement^ DatabaseImpl::CreateStatement(String^ sql) {
	
		FAIL_IF_CLOSED;

		if (sql == nullptr) {
			FAIL_IF_DEBUG;
			throw gcnew ArgumentException(gcnew String(L"sql string is null"));
		}

		pin_ptr<const wchar_t> pinned_sql = PtrToStringChars(sql);
		const wchar_t* const sql0 = pinned_sql;

		trcInfo(L"Creating statement for \"%s\" ...", sql0);

		::sqlite3_stmt* handle = NULL;
		const int rc = sqlite3_prepare16_v2(_hdl, sql0, -1, &handle, NULL);
		if (rc != SQLITE_OK) {
			const int errcode = ::sqlite3_extended_errcode(_hdl);
			const wchar_t* const errmsg = (const wchar_t*) ::sqlite3_errmsg16(_hdl);
			trcSqliteError(errcode, errmsg, L"Create SQL Statement failed. Statement was: \"%s\"", sql0);
			throw gcnew SqliteException(this, nullptr, errcode, errmsg, L"Create SQL Statement failed.");
		} 
		
		trcInfo(L"Created Statement 0x%p.", handle);
		
		StatementImpl^ statement = gcnew StatementImpl(this, handle, sql);

		return statement;	
	
	}
		
	void DatabaseImpl::StartTransaction() {
		this->ExecuteSql(gcnew String(L"BEGIN TRANSACTION"));
		_inTransaction = true;
	}

	void DatabaseImpl::CommitTransaction() {
		this->ExecuteSql(gcnew String(L"COMMIT TRANSACTION"));
		_inTransaction = false;
	}

	void DatabaseImpl::RollbackTransaction() {
		this->ExecuteSql(gcnew String(L"ROLLBACK TRANSACTION"));
		_inTransaction = false;
	}

} // end namespace sqlite

} // end namespace zencat